<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
	<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=utf-8">
	<TITLE>Выборка объектов (tut3.cpp)</TITLE>
	<META NAME="GENERATOR" CONTENT="LibreOffice 3.5  (Linux)">
	<META NAME="AUTHOR" CONTENT="Viacheslav Naydenov">
	<META NAME="CREATED" CONTENT="20130520;10025000">
	<META NAME="CHANGEDBY" CONTENT="Viacheslav Naydenov">
	<META NAME="CHANGED" CONTENT="20141022;571500">
	<STYLE TYPE="text/css">
	<!--
		@page { margin: 0.79in }
		P { margin-top: 0.04in; margin-bottom: 0.04in }
		H2 { margin-bottom: 0.08in }
		H2.western { font-family: "Times New Roman", serif }
		H2.cjk { font-family: "Droid Sans Fallback" }
		H2.ctl { font-family: "Lohit Hindi" }
		PRE.western { font-family: "Courier New", monospace }
		PRE.cjk { font-family: "Droid Sans Fallback", monospace }
		PRE.ctl { font-family: "Lohit Hindi", monospace }
		TT.western { font-family: "Courier New", monospace }
		TT.cjk { font-family: "Droid Sans Fallback", monospace }
		TT.ctl { font-family: "Lohit Hindi", monospace }
	-->
	</STYLE>
</HEAD>
<BODY LANG="ru-RU" DIR="LTR">
<H2 CLASS="western">Выборка объектов (tut3.cpp)</H2>
<P>Воспользуемся схемой данных из
<A HREF="Tutorial2.ru.html">Tutorial2</A>: «Отображение
связи таблиц один-ко-многим». Имеются
две таблицы: Клиенты (<TT CLASS="western">client_tbl</TT>)
и Заказы (<TT CLASS="western">order_tbl</TT>), у одного
клиента может быть ноль или более
заказов. Определим отображение таблиц
в классы и отношения классов в виде XML
файла:</P>
<PRE CLASS="western">&lt;schema&gt;
    &lt;table name=&quot;client_tbl&quot; sequence=&quot;client_seq&quot; class=&quot;Client&quot; xml-name=&quot;client&quot;&gt;
        &lt;column name=&quot;id&quot; type=&quot;longint&quot;&gt;
            &lt;primary-key /&gt;
        &lt;/column&gt;
        &lt;column name=&quot;dt&quot; type=&quot;datetime&quot; null=&quot;false&quot; default=&quot;sysdate&quot; /&gt;
        &lt;column name=&quot;name&quot; type=&quot;string&quot; size=&quot;100&quot; null=&quot;false&quot; /&gt;
        &lt;column name=&quot;email&quot; type=&quot;string&quot; size=&quot;100&quot; null=&quot;false&quot; /&gt;
        &lt;column name=&quot;phone&quot; type=&quot;string&quot; size=&quot;50&quot; null=&quot;true&quot; /&gt;
        &lt;column name=&quot;budget&quot; type=&quot;decimal&quot; /&gt;
    &lt;/table&gt;
    &lt;table name=&quot;order_tbl&quot; sequence=&quot;order_seq&quot; class=&quot;Order&quot; xml-name=&quot;order&quot;&gt;
        &lt;column name=&quot;id&quot; type=&quot;longint&quot;&gt;
            &lt;primary-key /&gt;
        &lt;/column&gt;
        &lt;column name=&quot;client_id&quot; type=&quot;longint&quot; null=&quot;false&quot;&gt;
            &lt;foreign-key table=&quot;client_tbl&quot; key=&quot;id&quot;/&gt;
        &lt;/column&gt;
        &lt;column name=&quot;dt&quot; type=&quot;datetime&quot; null=&quot;false&quot; default=&quot;sysdate&quot; /&gt;
        &lt;column name=&quot;memo&quot; type=&quot;string&quot; size=&quot;100&quot; /&gt;
        &lt;column name=&quot;total_sum&quot; type=&quot;decimal&quot; null=&quot;false&quot; /&gt;
        &lt;column name=&quot;paid_sum&quot; type=&quot;decimal&quot; default=&quot;0&quot; /&gt;
        &lt;column name=&quot;paid_dt&quot; type=&quot;datetime&quot; /&gt;
    &lt;/table&gt;
    &lt;relation type=&quot;one-to-many&quot;&gt;
        &lt;one class=&quot;Client&quot; property=&quot;orders&quot; /&gt;
        &lt;many class=&quot;Order&quot; property=&quot;owner&quot; /&gt;
    &lt;/relation&gt;
&lt;/schema&gt;</PRE><P>
Если необходимо, генерируем SQL код для
создания таблиц и прочих элементов
схемы данных. Указываем при генерации
целевой диалект SQL, на текущий момент
один из <TT CLASS="western">SQLITE</TT>, <TT CLASS="western">MYSQL</TT>,
<TT CLASS="western">POSTGRES</TT>, <TT CLASS="western">ORACLE</TT>,
<TT CLASS="western">INTERBASE</TT>. Полученный скрипт
можно накатить стандартным клиентом
для соответствующей БД, например <TT CLASS="western">sqlplus</TT>
для Oracle.</P>
<PRE CLASS="western" STYLE="margin-top: 0.04in; margin-bottom: 0.2in">$(YBORM_ROOT)/bin/yborm_gen --ddl tutorial/tut3.xml MYSQL script.sql</PRE><P>
Генерируем доменные классы с помощью
той же утилиты, но с другим ключом. Если
классы уже были созданы, но со временем
состав полей в таблицах поменялся, то
можно использовать утилиту для обновления
состава атрибутов в доменных классах.
</P>
<PRE CLASS="western" STYLE="margin-top: 0.04in; margin-bottom: 0.2in">$(YBORM_ROOT)/bin/yborm_gen --domain tutorial/tut3.xml domain/</PRE><P>
Для работы с базой данных нам понадобится
объект <TT CLASS="western">Engine</TT>, который берет
на себя все операции с уровнем SQL. Объекту
<TT CLASS="western">Engine</TT> для работы необходимо
предоставить 
</P>
<OL>
	<LI><P>либо отдельный объект-подключение
	к базе <TT CLASS="western">SqlConnection</TT>, через
	который будут выполняться SQL операторы;
		</P>
	<LI><P>либо пул подключений <TT CLASS="western">SqlPool</TT>,
	который сам может управлять соединениями,
	выдавая из пула соединение по требованию
	или создавая новое. 
	</P>
</OL>
<P>В примере ниже объект <TT CLASS="western">Engine</TT>
использует одно явно заданное подключение.
Поверх <TT CLASS="western">Engine</TT> работает объект
<TT CLASS="western">Session</TT>, он отслеживает
состояние всех объектов, выполняет
загрузку и сохранение объектов, когда
требуется. 
</P>
<PRE CLASS="western">    auto_ptr&lt;SqlConnection&gt; conn(
        new SqlConnection(&quot;mysql+odbc://test1_usr:test1_pwd@test1_dsn&quot;));
    Engine engine(Engine::READ_WRITE, conn);
    Session session(init_schema(), &amp;engine);</PRE><P>
Для краткости, в один из конструкторов
<TT CLASS="western">Session</TT>, можно также передать
строку параметров для подключения. 
</P>
<PRE CLASS="western" STYLE="margin-top: 0.04in; margin-bottom: 0.2in">    Session session(init_schema(), &quot;mysql+odbc://test1_usr:test1_pwd@test1_dsn&quot;);</PRE><P>
Рассмотрим несколько примеров запросов.
<STRONG>Простейший случай: выбрать объект
по его первичному ключу</STRONG>. Для этого
можно просто сконструировать доменный
объект с параметром-первичным ключом.
Этот доменный объект автоматически
подгрузит все свои атрибуты при первом
обращении к ним. До этого момента он
пребывает в так называемом «призрачном»
состоянии. Этот приём называется «ленивой
загрузкой». Может и так оказаться, что
объект с указанным значением первичного
ключа не существует в базе, в этом случае
при обращении к атрибуту будет брошено
исключение. 
</P>
<PRE CLASS="western">    Client client(session, 32738);
    try {
      cout &lt;&lt; client.name.value() &lt;&lt; endl;
    } catch (NoDataFound &amp;) {
      cerr &lt;&lt; &quot;No such client\n&quot;;
    }
SQL:
SELECT client_tbl.id, client_tbl.dt, client_tbl.name, client_tbl.email, client_tbl.budget FROM client_tbl WHERE client_tbl.id = ?</PRE><P>
Часто требуется выбрать объект по
произвольному условию, которое не
обязательно может быть фильтром по
первичному ключу. Вот <STRONG>как можно
запросить один объект Клиент, отфильтровав
по имени.</STRONG> 
</P>
<PRE CLASS="western">    Client client = query&lt;Client&gt;(session)
      .filter_by(Client::c.name == name).one();
SQL:
SELECT client_tbl.id, client_tbl.dt, client_tbl.name, client_tbl.email, client_tbl.budget FROM client_tbl WHERE client_tbl.name = ?</PRE><P>
Шаблонная функция <TT CLASS="western">query&lt;R&gt;()</TT>
возвращает объект-запрос <TT CLASS="western">QueryObj</TT>,
к которому можно добавлять условия
фильтрации и сортировки. В качестве
параметра <TT CLASS="western">R</TT> можно указать
доменный класс. Для выборки одного
объекта используйте метод <TT CLASS="western">one()</TT>
класса <TT CLASS="western">QueryObj</TT>. Если объект
не будет найден или объектов будет
больше чем один, в обоих случаях будет
брошено исключение <TT CLASS="western">NoDataFound</TT>.
Для выборки всех объектов по какому-либо
критерию используйте метод <TT CLASS="western">all()</TT>
класса <TT CLASS="western">QueryObj</TT>, возвращающий
коллекцию, по которой можно пройти в
цикле, используя итераторы. Итератор
является «ленивым», он достаёт очередную
запись из курсора при обращении к ней.
<STRONG>Пример: выбрать все неоплаченные
заказы, у которых сумма больше определённой.</STRONG>
</P>
<PRE CLASS="western">    DomainResultSet&lt;Order&gt; rs = query&lt;Order&gt;(session)
      .filter_by(Order::c.total_sum &gt; Decimal(100)
           &amp;&amp; Order::c.paid_dt == YB_NULL).all();
    BOOST_FOREACH(Order order, rs) {
      cout &lt;&lt; order.id.value() &lt;&lt; &quot;,&quot;;
    }
SQL:
SELECT order_tbl.id, order_tbl.client_id, order_tbl.dt, order_tbl.memo, 
 order_tbl.total_sum, order_tbl.paid_sum, order_tbl.paid_dt 
FROM order_tbl WHERE (order_tbl.total_sum &gt; ?) AND (order_tbl.paid_dt IS NULL)</PRE><P>
Для объекта <TT CLASS="western">QueryObj</TT> можно
применять и метод <TT CLASS="western">count()</TT>.
Этот вызов преобразует запрос в подзапрос,
по которому подсчитывается количество
элементов. Данный метод возвращает
целое число. <STRONG>Пример: посчитать
количество заказов заданного клиента.</STRONG>
</P>
<PRE CLASS="western">    cout &lt;&lt; &quot;Order count: &quot; &lt;&lt; query&lt;Order&gt;(session)
      .filter_by(Order::c.client_id == 32738).count() &lt;&lt; endl;
SQL:
SELECT COUNT(*) CNT FROM (
SELECT order_tbl.id, order_tbl.client_id, order_tbl.dt, order_tbl.memo, order_tbl.total_sum,
 order_tbl.paid_sum, order_tbl.paid_dt 
FROM order_tbl WHERE order_tbl.client_id = ?) X</PRE><P>
Часто бывает необходимо реализовать
механизм прокрутки страниц, для этого
нужно получить не весь результат запроса,
в только строки для показа на определенной
странице. У объекта <TT CLASS="western">QueryObj</TT>
можно вызвать метод <TT CLASS="western">range()</TT>.
Этот метод модифицирует запрос с учетом
специфики текущего диалекта SQL. Первый
параметр указывает начальную позицию
диапазона (элементы нумеруются с 0),
второй элемент — конечную позицию (не
включается).  <STRONG>Пример: пейджер по
пользовательским заказам, вывести
элементы с 30 по 39.</STRONG> 
</P>
<PRE CLASS="western">    DomainResultSet&lt;Order&gt; rs = query&lt;Order&gt;(session)
      .filter_by(Order::c.client_id == 32738)
      .order_by(Order::c.dt)
      .range(30, 40).all();
    BOOST_FOREACH(Order order, rs) {
      cout &lt;&lt; order.id &lt;&lt; &quot;,&quot;;
    }
SQL:
SELECT order_tbl.id, order_tbl.client_id, order_tbl.dt, order_tbl.memo, 
 order_tbl.total_sum, order_tbl.paid_sum, order_tbl.paid_dt 
FROM order_tbl WHERE (order_tbl.client_id = ?)
ORDER BY order_tbl.dt
LIMIT ? OFFSET ?
params: (32738, 10, 30)
</PRE><P>
Иногда бывают нужны более сложные
запросы, включающие связки таблиц по
внешнему ключу. В YB.ORM параметром шаблонной
функции <TT CLASS="western">query&lt;R&gt;()</TT> может
быть не только доменный объект, но и
кортеж (<TT CLASS="western">boost::tuple</TT>) из нескольких
доменных классов, порядок важен. В этом
случае между каждой парой соседних
классов из указанного списка должно
существовать явное отношение, чтобы
можно было вычислить условие связки
таблиц. <STRONG>Предположим, нужно извлечь
все заказы, с фильтром по сумме, вместе
с их соответствующими клиентами.
Результат отсортировать по ID клиента,
затем заказа.</STRONG> 
</P>
<PRE CLASS="western">    typedef tuple&lt;Order, Client&gt; Pair; 
    DomainResultSet&lt;Pair&gt; rs = query&lt;Pair&gt;(session) 
      .filter_by(Order::c.total_sum &gt; Decimal(&quot;3.14&quot;) 
           &amp;&amp; Client::c.budget != YB_NULL)
      .order_by(ExpressionList(Client::c.id, Order::c.id)).all(); 
    BOOST_FOREACH(Pair pair, rs) { 
      cout &lt;&lt; pair.get&lt;1&gt;().name.value() 
        &lt;&lt; &quot; &quot; &lt;&lt; pair.get&lt;0&gt;().total_sum.value() &lt;&lt; endl; 
    } 
SQL:
SELECT order_tbl.id, order_tbl.client_id, order_tbl.dt, order_tbl.memo, order_tbl.total_sum,
 order_tbl.paid_sum, order_tbl.paid_dt, 
 client_tbl.id, client_tbl.dt, client_tbl.name, client_tbl.email, client_tbl.budget 
FROM order_tbl JOIN client_tbl ON (client_tbl.ID = order_tbl.client_id) 
WHERE (order_tbl.total_sum &gt; ?) AND (client_tbl.budget IS NOT NULL) 
ORDER BY client_tbl.id, order_tbl.id </PRE><P>
В данном случае ленивая коллекция <TT CLASS="western">rs</TT>
будет возвращать пары объектов Заказ,
Клиент. 
</P>
</BODY>
</HTML>